#region Namespaces

using System;
using System.Collections;
using System.Collections.Generic;
using System.Xml;

#endregion

namespace Vibstudio.X4NET.Xml.Linq
{
    public sealed class XNodeEqualityComparer : IEqualityComparer, IEqualityComparer<XNode>
    {
        bool IEqualityComparer.Equals(object n1, object n2)
        {
            return Equals((XNode)n1, (XNode)n2);
        }

        int IEqualityComparer.GetHashCode(object obj)
        {
            return GetHashCode((XNode)obj);
        }

        public bool Equals(XNode x, XNode y)
        {
            if (x == null)
            {
                return y == null;
            }

            if (y == null)
            {
                return false;
            }

            if (x.NodeType != y.NodeType)
            {
                return false;
            }
            switch (x.NodeType)
            {
                case XmlNodeType.Document:
                    XDocument doc1 = (XDocument)x;
                    XDocument doc2 = (XDocument)y;
                    if (!Equals(doc1.Declaration, doc2.Declaration))
                    {
                        return false;
                    }
                    IEnumerator<XNode> id2 = doc2.Nodes().GetEnumerator();
                    foreach (XNode n in doc1.Nodes())
                    {
                        if (!id2.MoveNext())
                        {
                            return false;
                        }
                        if (!Equals(n, id2.Current))
                        {
                            return false;
                        }
                    }
                    return !id2.MoveNext();
                case XmlNodeType.Element:
                    XElement e1 = (XElement)x;
                    XElement e2 = (XElement)y;
                    if (e1.Name != e2.Name)
                    {
                        return false;
                    }
                    IEnumerator<XAttribute> ia2 = e2.Attributes().GetEnumerator();
                    foreach (XAttribute n in e1.Attributes())
                    {
                        if (!ia2.MoveNext())
                        {
                            return false;
                        }
                        if (!Equals(n, ia2.Current))
                        {
                            return false;
                        }
                    }
                    if (ia2.MoveNext())
                    {
                        return false;
                    }
                    IEnumerator<XNode> ie2 = e2.Nodes().GetEnumerator();
                    foreach (XNode n in e1.Nodes())
                    {
                        if (!ie2.MoveNext())
                        {
                            return false;
                        }
                        if (!Equals(n, ie2.Current))
                        {
                            return false;
                        }
                    }
                    return !ie2.MoveNext();
                case XmlNodeType.Comment:
                    XComment c1 = (XComment)x;
                    XComment c2 = (XComment)y;
                    return c1.Value == c2.Value;
                case XmlNodeType.ProcessingInstruction:
                    XProcessingInstruction p1 = (XProcessingInstruction)x;
                    XProcessingInstruction p2 = (XProcessingInstruction)y;
                    return p1.Target == p2.Target && p1.Data == p2.Data;
                case XmlNodeType.DocumentType:
                    XDocumentType d1 = (XDocumentType)x;
                    XDocumentType d2 = (XDocumentType)y;
                    return d1.Name == d2.Name &&
                           d1.PublicId == d2.PublicId &&
                           d1.SystemId == d2.SystemId &&
                           d1.InternalSubset == d2.InternalSubset;
                case XmlNodeType.Text:
                    return ((XText)x).Value == ((XText)y).Value;
            }
            throw new Exception("INTERNAL ERROR: should not happen");
        }

        public int GetHashCode(XNode obj)
        {
            int h = ((int)obj.NodeType << 6);
            switch (obj.NodeType)
            {
                case XmlNodeType.Document:
                    XDocument doc = (XDocument)obj;
                    h = h ^ GetHashCode(doc.Declaration);
                    foreach (XNode n in doc.Nodes())
                    {
                        h = h ^ (n.GetHashCode() << 5);
                    }
                    break;
                case XmlNodeType.Element:
                    XElement el = (XElement)obj;
                    h = h ^ (el.Name.GetHashCode() << 3);
                    foreach (XAttribute a in el.Attributes())
                    {
                        h = h ^ (a.GetHashCode() << 7);
                    }
                    foreach (XNode n in el.Nodes())
                    {
                        h = h ^ (n.GetHashCode() << 6);
                    }
                    break;
                case XmlNodeType.Comment:
                    h = h ^ ((XComment)obj).Value.GetHashCode();
                    break;
                case XmlNodeType.ProcessingInstruction:
                    XProcessingInstruction pi = (XProcessingInstruction)obj;
                    h = h ^ ((pi.Target.GetHashCode() << 6) + pi.Data.GetHashCode());
                    break;
                case XmlNodeType.DocumentType:
                    XDocumentType dtd = (XDocumentType)obj;
                    h = h ^ (dtd.Name.GetHashCode() << 7) ^
                        (dtd.PublicId.GetHashCode() << 6) ^
                        (dtd.SystemId.GetHashCode() << 5) ^
                        (dtd.InternalSubset.GetHashCode() << 4);
                    break;
                case XmlNodeType.Text:
                    h = h ^ (obj.GetHashCode());
                    break;
            }
            return h;
        }

        private bool Equals(XAttribute a1, XAttribute a2)
        {
            if (a1 == null)
            {
                return a2 == null;
            }
            if (a2 == null)
            {
                return false;
            }
            return a1.Name == a2.Name && a1.Value == a2.Value;
        }

        private bool Equals(XDeclaration d1, XDeclaration d2)
        {
            if (d1 == null)
            {
                return d2 == null;
            }
            if (d2 == null)
            {
                return false;
            }
            return d1.Version == d2.Version &&
                   d1.Encoding == d2.Encoding &&
                   d1.Standalone == d2.Standalone;
        }

        private int GetHashCode(XDeclaration d)
        {
            if (d == null)
            {
                return 0;
            }
            return (d.Version.GetHashCode() << 7) ^
                   (d.Encoding.GetHashCode() << 6) ^
                   d.Standalone.GetHashCode();
        }
    }
}